package org.jeecg.modules.iot.service.impl;

import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.iot.model.v20180120.*;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.jeecg.common.exception.ChechDataAuthException;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.iot.aio.Aio;
import org.jeecg.modules.iot.aio.AioConfiguration;
import org.jeecg.modules.iot.aio.ChannelContext;
import org.jeecg.modules.iot.aliyun.DeviceConfig;
import org.jeecg.modules.iot.aliyun.MessageBody;
import org.jeecg.modules.iot.common.IotConst;
import org.jeecg.modules.iot.aliyun.DeviceOnline;
import org.jeecg.modules.iot.message.Powerbank;
import org.jeecg.modules.iot.message.ReceivePopupSN;
import org.jeecg.modules.iot.message.ReceiveUpload;
import org.jeecg.modules.iot.service.IotDeviceService;
import org.jeecg.modules.iot.tools.HmacCoder;
import org.jeecg.modules.iot.tools.JsonUtils;
import org.jeecg.modules.iot.util.IotClientUtil;
import org.jeecg.modules.iot.util.MnsUtil;
import org.jeecg.modules.power.device.entity.YlDevice;
import org.jeecg.modules.power.device.entity.YlDeviceCategory;
import org.jeecg.modules.power.device.service.IYlDeviceCategoryService;
import org.jeecg.modules.power.device.service.IYlDeviceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static org.apache.commons.codec.binary.Base64.encodeBase64String;

/**
 * 物联网设备业务处理层
 *
 * @author scw
 * @version v1.0
 * @date
 */
@Service
@RequiredArgsConstructor
public class IotDeviceServiceImpl implements IotDeviceService {

    private final IotClientUtil iotClientUtil;
    private final MnsUtil mnsUtil;
    private final RedisTemplate redisTemplate;
    private final IYlDeviceService deviceService;
    private final IYlDeviceCategoryService deviceCategoryService;


    /*****************************设备关联的查询***************************************************************************************************************/

    /**
     * 根据设备SN号查询绑定设备的轮巡充数
     * @param deviceSn
     * @return
     */
    public Integer getDevice(String deviceSn){
        QueryWrapper<YlDevice> deviceQueryWrapper = new QueryWrapper<>();
        deviceQueryWrapper.eq("device_sn",deviceSn).last("limit 1");
        YlDevice device = deviceService.getOne(deviceQueryWrapper);
        if(oConvertUtils.isEmpty(device))throw new ChechDataAuthException("未查询到关联设备信息");
        YlDeviceCategory category = deviceCategoryService.getById(device.getDeviceCategory());
        return category.getRotationNumber();
    }

    /*****************************设备关联的查询***************************************************************************************************************/


    /*****************************aliyun物联网相关代码***************************************************************************************************************/
    //region


    /**
     * 注册设备
     */
    public void registerDevice(String deviceName) throws Exception {
        DefaultAcsClient client = iotClientUtil.getDefaultAcsClient();
        com.aliyuncs.iot.model.v20180120.RegisterDeviceRequest request = new com.aliyuncs.iot.model.v20180120.RegisterDeviceRequest();
        request.setProductKey(iotClientUtil.getProductKey());
        request.setDeviceName(deviceName);
        request.setIotInstanceId(iotClientUtil.getIotInstanceId());
        RegisterDeviceResponse response = client.getAcsResponse(request);
        if (!response.getSuccess()) {
            throw new ClientException(response.getErrorMessage());
        }
    }

    /**
     * 删除设备
     */
    public void delDevice(String deviceName) throws Exception {
        DefaultAcsClient client = iotClientUtil.getDefaultAcsClient();
        DeleteDeviceRequest request = new DeleteDeviceRequest();
        request.setProductKey(iotClientUtil.getProductKey());
        request.setDeviceName(deviceName);
        request.setIotInstanceId(iotClientUtil.getIotInstanceId());
        DeleteDeviceResponse response = client.getAcsResponse(request);
        if (!response.getSuccess()) {
            throw new ClientException(response.getErrorMessage());
        }
    }

    /**
     * 查询设备信息
     * @param deviceName
     * @return
     * @throws Exception
     */
    public QueryDeviceInfoResponse queryDevice(String deviceName) throws Exception {
        QueryDeviceInfoRequest requests = new QueryDeviceInfoRequest();
        requests.setProductKey(iotClientUtil.getProductKey());
        requests.setDeviceName(deviceName);
        requests.setIotInstanceId(iotClientUtil.getIotInstanceId());
        try {
            QueryDeviceInfoResponse response = iotClientUtil.getDefaultAcsClient().getAcsResponse(requests);
            return response;
        }catch (ClientException e){
            QueryDeviceInfoResponse response = new QueryDeviceInfoResponse();
            response.setSuccess(false);
            response.setCode(e.getErrCode());
            response.setErrorMessage(e.getMessage());
            response.setRequestId(e.getRequestId());
            return response;
        }
    }

    /**
     * 获取MQTT鉴权地址
     * https://help.aliyun.com/document_detail/73742.html?spm=a2c4g.11186623.6.587.69fa1bb8kGHsBc
     * 使用HTTPS认证再连接
     * @param deviceName 设备名称
     * @return 阿里云返回设备的登录MQTT的账号和密码
     * @throws Exception 异常
     */
    public DeviceConfig getIotDeviceConfig( String deviceName) throws Exception {

        QueryWrapper<YlDevice> deviceQueryWrapper = new QueryWrapper<>();
        deviceQueryWrapper.eq("device_sn",deviceName).last("limit 1");
        YlDevice device = deviceService.getOne(deviceQueryWrapper);
        if(oConvertUtils.isEmpty(device))throw new ChechDataAuthException("未查询到设备信息，请先绑定设备！");

        DeviceConfig iotDeviceConfig = new DeviceConfig();
        //设备详细信息
        QueryDeviceInfoResponse query = queryDevice(deviceName);

        //阿里云注册设备Iot.Device.NotExistedDevice
        if (!query.getSuccess() && "Iot.Device.NotExistedDevice".equals(query.getCode())) {
            registerDevice(deviceName);
            //新激活的设备
            query = queryDevice(deviceName);
        }

        //设备详细信息
//        QueryDeviceByNameResponse.DeviceInfo deviceInfo = query.getDeviceInfo();
        QueryDeviceInfoResponse.Data deviceInfo = query.getData();

        //获取新的账号，密码
        iotDeviceConfig.setSn(deviceName);
        iotDeviceConfig.setProductKey(iotClientUtil.getProductKey());
        iotDeviceConfig.setDeviceName(iotDeviceConfig.getSn());
        iotDeviceConfig.setDeviceSecret(deviceInfo.getDeviceSecret());
        iotDeviceConfig.setHost(iotDeviceConfig.getProductKey() + ".iot-as-mqtt." + iotClientUtil.getRegionId() + ".aliyuncs.com");
        iotDeviceConfig.setPort(1883);
        iotDeviceConfig.setCreatedTime(new Date());

        //七天过期，这里保险设置6天
        iotDeviceConfig.setOverdueTime(DateUtils.addDays(new Date(), 6));

        StringBuffer sb = new StringBuffer();
        sb.append("clientId" + iotDeviceConfig.getDeviceName());
        sb.append("deviceName" + iotDeviceConfig.getDeviceName());
        sb.append("productKey" + iotDeviceConfig.getProductKey());

        //公共参数签名
        String sign = HmacCoder.encrypt(sb.toString(), iotDeviceConfig.getDeviceSecret(), HmacCoder.TYPE_HMAC_MD5);
        //创建httpclient对象
        CloseableHttpClient client = HttpClients.createDefault();
        HttpPost httpPost = new HttpPost("https://iot-auth." + iotClientUtil.getRegionId()+".aliyuncs.com/auth/devicename");
        //装填参数
        List<BasicNameValuePair> nvps = new ArrayList<BasicNameValuePair>();
        nvps.add(new BasicNameValuePair("productKey", iotDeviceConfig.getProductKey()));
        nvps.add(new BasicNameValuePair("sign", sign));
        nvps.add(new BasicNameValuePair("deviceName", iotDeviceConfig.getDeviceName()));
        nvps.add(new BasicNameValuePair("clientId", iotDeviceConfig.getDeviceName()));
        nvps.add(new BasicNameValuePair("signmethod", "hmacmd5"));
        //设置参数到请求对象中
        httpPost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
        //设置header信息
        httpPost.setHeader("Content-type", "application/x-www-form-urlencoded");
        httpPost.setHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
        //执行请求操作，并拿到结果（同步阻塞）
        CloseableHttpResponse response = client.execute(httpPost);
        //获取结果实体
        HttpEntity entity = response.getEntity();
        String body = null;
        if (entity != null) {
            //按指定编码转换结果实体为String类型
            body = EntityUtils.toString(entity, HTTP.UTF_8);
        }
        EntityUtils.consume(entity);

        //释放链接
        response.close();
        int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode != HttpStatus.SC_OK) {
            throw new ClientException("IOT CONNECT ERROR " + statusCode);
        }

        LinkedHashMap result = JsonUtils.toObject(body, LinkedHashMap.class);

        Map data = MapUtils.getMap(result, "data");
        String iotId = MapUtils.getString(data, "iotId");
        String iotToken = MapUtils.getString(data, "iotToken");

        iotDeviceConfig.setIotId(iotId);
        iotDeviceConfig.setIotToken(iotToken);
        return iotDeviceConfig;
    }
    //endregion
    /*****************************aliyun物联网相关代码***************************************************************************************************************/

    /**
     * 整机上报
     * @param rentBoxSN
     * @param messageBody
     */
    public void uploadData(String rentBoxSN,MessageBody messageBody){
        AioConfiguration configuration = Aio.getAioConfiguration(iotClientUtil.getProductKey());
        ChannelContext channelContext = configuration.queueList.getChannelContext(rentBoxSN,configuration);
        channelContext.addMNSMessage(messageBody);
    }


    /**
     * 发送异步消息
     */
    public PubResponse sendMsg(String productKey, String topicFullName, byte[] bytes, int qos) throws Exception {
        PubRequest request = new PubRequest();

        String base64 = encodeBase64String(bytes);

        request.setProductKey(productKey);
        request.setMessageContent(base64);
        request.setTopicFullName(topicFullName);
        request.setIotInstanceId(iotClientUtil.getIotInstanceId());
        request.setQos(qos > 1 ? 1 : qos); //目前支持QoS0和QoS1

        PubResponse response = iotClientUtil.getDefaultAcsClient().getAcsResponse(request);

        //TEST
        MessageBody messageBody = new MessageBody();
        messageBody.setMessageId("send_message");
        messageBody.setMessageType("send");
        messageBody.setTopic(topicFullName);
        messageBody.setPayload(new String(bytes, StandardCharsets.UTF_8));
        messageBody.setTimestamp(System.currentTimeMillis() / 1000);


        return response;
    }

    /**
     * 发送异步消息
     */
    public PubResponse sendMsgAsync(String deviceName, String messageContent, int qos) throws ClientException {
        byte[] bytes = messageContent.getBytes(StandardCharsets.UTF_8);
        String topicFullName = String.format(IotConst.topic_get,iotClientUtil.getProductKey(),deviceName);
        PubResponse response = sendMsgAsync(iotClientUtil.getProductKey(), topicFullName, bytes, qos);
        return response;
    }

    /**
     * 发送异步消息
     */
    public PubResponse sendMsgAsync(String productKey, String topicFullName, byte[] bytes, int qos) throws ClientException {
        PubRequest request = new PubRequest();
        String base64 = encodeBase64String(bytes);
        request.setProductKey(iotClientUtil.getProductKey());
        request.setMessageContent(base64);
        request.setTopicFullName(topicFullName);
        request.setIotInstanceId(iotClientUtil.getIotInstanceId());
        request.setQos(qos > 1 ? 1 : qos); //目前支持QoS0和QoS1

        DefaultAcsClient client = iotClientUtil.getDefaultAcsClient();

        PubResponse response = client.getAcsResponse(request);

        //TEST
        MessageBody messageBody = new MessageBody();
        messageBody.setMessageId("send_message");
        messageBody.setMessageType("send");
        messageBody.setTopic(topicFullName);
        messageBody.setPayload(new String(bytes, StandardCharsets.UTF_8));
        messageBody.setTimestamp(System.currentTimeMillis() / 1000);
//        mnsUtils.putMessageBody(messageBody);
        return response;
    }


    public ReceiveUpload check(String rentboxSN) throws Exception {
        String key = "check:" + rentboxSN;
        byte[] data = sendPopupWait(key, rentboxSN, IotConst.SEND_CHECK, 10);

        ReceiveUpload receiveUpload = new ReceiveUpload(data);
        return receiveUpload;
    }

    public ReceiveUpload checkAll(String rentboxSN) throws Exception {
        String key = "check:" + rentboxSN;
        byte[] data = sendPopupWait(key, rentboxSN, IotConst.SEND_CHECK_ALL,  10);
        ReceiveUpload receiveUpload = new ReceiveUpload(data);
        return receiveUpload;
    }

    /**
     * SN号弹出
     * @param rentboxSN
     * @param singleSN
     * @return
     * @throws Exception
     */
    public ReceivePopupSN popup(String rentboxSN, String singleSN) throws Exception {
        String key = "popup_sn:" + rentboxSN;
        String message = String.format(IotConst.SEND_POPUP_SN, singleSN);
        byte[] data = sendPopupWait(key, rentboxSN, message,  15); //尝试发送2次，超时8秒

        ReceivePopupSN receivePopupSN = new ReceivePopupSN(data);
        return receivePopupSN;
    }


    private byte[] sendPopupWait(String key, String rentboxSN, String message, int overSecound) throws Exception {
        this.checkOnlineStatus(rentboxSN);

        //PUT REDIS
        BoundValueOperations operations = redisTemplate.boundValueOps(key);
        operations.set(null, overSecound, TimeUnit.SECONDS);

        String topic = "/" + iotClientUtil.getProductKey() + "/" + rentboxSN  + "/user/get";
        iotClientUtil.sendMsgAsync(iotClientUtil.getProductKey(), topic, message, 1 );

        byte[] bytes = null;
        for(int i = 0; i < overSecound * 2; i ++){
            Thread.sleep(500);
            Object data = null;
            try {
                data = operations.get();
            }
            catch (Exception e){
                e.printStackTrace();
            }
            if(data != null && data instanceof byte[]){
                bytes = (byte[]) data;
                redisTemplate.boundValueOps(key).expire(-1, TimeUnit.MILLISECONDS);
                break;
            }
        }

        if(bytes == null){
            throw new Exception("Request Time Out");
        }

        return bytes;
    }

    public void checkOnlineStatus(String rentboxSN) throws Exception {
        DeviceOnline onlineStatus = iotClientUtil.getDeviceStatus(iotClientUtil.getProductKey(), rentboxSN);
        if(!onlineStatus.name().equals("ONLINE")){
            throw new Exception("Device is Offline");
        }
    }

    public ReceivePopupSN popupByRandom(String rentboxSN, int minPower) throws Exception {
        //check
        ReceiveUpload receiveUpload = check(rentboxSN);
        Powerbank powerbank = receiveUpload.getPowerbankByRandom(minPower);
        if(powerbank == null){
            throw new Exception("NO Powerbank");
        }

        //popup
        ReceivePopupSN receivePopupSN = popup(rentboxSN, powerbank.getSnAsString());
        return receivePopupSN;
    }
}

